/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
 *
 */

import fse from 'fs-extra';
import { glob } from 'glob';
import os from 'os';
import path from 'path';

import { NativeConst } from '../const/common-const.js';
import { CpuAbiEnum } from '../enum/cpu-abi-enum.js';
import { PkgSelection } from '../options/build/build-opt.js';
import { Dependency } from '../project/dependency/core/dependency-interface.js';
import { TargetTaskService } from '../tasks/service/target-task-service.js';

import { OhosLogger } from './log/ohos-logger.js';
import { CmakeUtil } from './cmake-util.js';
import { parseJsonFile } from '@ohos/hvigor';

const log = OhosLogger.getLogger('ConfigureCmake');

/**
 * 为依赖生成cmake脚本生成依赖模型
 */
export class PackageResolver {
  name: string;
  version: string;
  path: string;
  headerPath: string[];
  libsPath: string;
  archs: ArchResolver[] = [];
  cmakeDir: string;
  canonicalName: string;
  abiList: string[];
  service?: TargetTaskService;
  matchRule?: PkgSelection;
  linkLibrariesMap: Map<string, string> = new Map<string, string>();

  constructor(dependency: Dependency, abiList: string[], cmakeDir: string, service?: TargetTaskService) {
    this.canonicalName = dependency.getPackageName();
    this.name = this.canonicalName.replace(NativeConst.PACKAGE_NAME_REGEX, '');
    this.version = dependency.getDependencyVersion();
    this.abiList = abiList;
    this.path = dependency.getDependencyRootPath();
    this.cmakeDir = cmakeDir;
    this.service = service;
    this.headerPath = this.getHeaderPath(dependency, service);
    this.libsPath = service
      ? service.getTargetData().getPathInfo().getIntermediatesStrippedLibsDir()
      : path.resolve(dependency.getDependencyRootPath(), 'libs');
    this.matchRule = (service?.getBuildOption().nativeLib?.filter?.select ?? []).find(
      (rule) => rule.package === this.canonicalName && rule.version === this.version,
    );
    this.addLibrariesInfo(dependency.getPackageFilePath(), dependency.isLocal());
    this.archs = this.abiList.map((abi) => ArchResolver.getInstance(abi, this));
  }

  static getInstance(dep: Dependency, abiList: string[], cmakeDir: string, service?: TargetTaskService) {
    return new PackageResolver(dep, abiList, cmakeDir, service);
  }

  async build() {
    await Promise.all(this.archs.map((arch) => arch.build()));
  }

  addLibrariesInfo(ohPackageJson5Path: string, isLocal: boolean): void {
    const librariesInfo = this.service?.getBuildOption()?.nativeLib?.librariesInfo;
    if (librariesInfo && librariesInfo.length > 0) {
      for (const library of librariesInfo) {
        if (!library.linkLibraries) {
          continue;
        }
        this.linkLibrariesMap.set(library.name, library.linkLibraries.join(';'));
      }
    }
    // 远程透传so进行拼接
    if (isLocal || !fse.existsSync(ohPackageJson5Path)) {
      return;
    }
    const ohPkgJson = parseJsonFile(ohPackageJson5Path);
    const nativeComponents = ohPkgJson.nativeComponents;
    if (nativeComponents && nativeComponents.length > 0) {
      for (const component of nativeComponents) {
        if (!component.name || !component.linkLibraries) {
          continue;
        }
        this.linkLibrariesMap.set(component.name, component.linkLibraries.join(';'));
      }
    }
  }

  private getHeaderPath(dependency: Dependency, service?: TargetTaskService) {
    const headerPath = service?.getBuildOption().nativeLib?.headerPath;
    if (!headerPath) {
      return [path.resolve(dependency.getDependencyRootPath(), NativeConst.INCLUDE_DIR)];
    }
    if (typeof headerPath === 'string') {
      return path.isAbsolute(headerPath) ? [headerPath] : [path.resolve(dependency.getDependencyRootPath(), headerPath)];
    } else {
      // 处理字符串数组的情况，例如对每个元素进行处理
      const resolvedPaths = headerPath.map((pathItem) => {
        return path.isAbsolute(pathItem) ? pathItem : path.resolve(dependency.getDependencyRootPath(), pathItem);
      });
      return resolvedPaths;
    }
  }
}

export class ArchResolver {
  arch: string;
  path: string;
  pkg: PackageResolver;
  libraries: LibraryResolver[] = [];
  cmakeFile: string;
  cmakeText = '';

  constructor(arch: string, pkg: PackageResolver) {
    this.arch = arch;
    this.pkg = pkg;
    this.path = path.resolve(this.pkg.libsPath, arch);
    this.cmakeFile = path.resolve(pkg.cmakeDir, arch, 'lib', CpuAbiEnum.getCpuType(arch), 'cmake', pkg.name, `${pkg.name}Config.cmake`);
  }

  static getInstance(arch: string, pkg: PackageResolver) {
    return new ArchResolver(arch, pkg);
  }

  async build() {
    this.libraries = await this.calLibraries();
    this.cmakeText = this.libraries.map((lib) => lib.text).join(os.EOL);
    if (!fse.existsSync(this.cmakeFile)) {
      await fse.ensureDir(path.dirname(this.cmakeFile));
      await fse.writeFile(this.cmakeFile, this.cmakeText);
      return this;
    }
    const originalText = await fse.readFile(this.cmakeFile, { encoding: 'utf8' });
    if (originalText !== this.cmakeText) {
      await fse.rm(this.cmakeFile, { force: true });
      await fse.writeFile(this.cmakeFile, this.cmakeText);
    }
    return this;
  }

  /**
   * 本地模块读取cmake codemodel
   * 远程模块读取安装目录
   */
  async calLibraries() {
    if (!this.pkg.service) {
      if (fse.existsSync(this.path)) {
        log.debug(`Reading libs dir ${this.path}`);
        const libraries = glob.sync(NativeConst.LIBRARY_FILE_PATTERN, { cwd: this.path });
        return libraries
          .filter((lib) => path.basename(lib) !== NativeConst.SHARED_STL_LIBRARY)
          .map((lib) => LibraryResolver.getInstance(path.basename(lib), path.resolve(this.path, lib), this));
      }
      log.debug('No available libraries.');
      return [];
    }

    const targetData = this.pkg.service.getTargetData();
    const workDir = targetData.getPathInfo().getNinjaWorkDir();
    const archDir = path.resolve(workDir, this.arch);
    log.debug(`Local module ${this.pkg.name}, reading cmake code model in ${archDir}.`);
    if (!fse.existsSync(archDir)) {
      log.warn(`not found cmake code model in ${archDir}`);
      return [];
    }

    const libraries = CmakeUtil.parseLibraries(archDir, targetData.getTargetName(), this.arch);
    const libraryModels = [...libraries.values()].filter((model) => model.getType() === 'STATIC_LIBRARY' || model.getType() === 'SHARED_LIBRARY');
    const files = libraryModels.flatMap((model) => model.getOutputs() ?? []);
    if (this.pkg.service.getModuleService().getModuleModel().isHspModule()) {
      files.push(...libraryModels.flatMap((model) => model.getRuntimeFiles() ?? []));
    }
    return files
      .map((file) => (path.isAbsolute(file) ? file : path.resolve(workDir, this.arch, file)))
      .filter((file) => file.includes('.so') || file.includes('.a'))
      .map((file) => LibraryResolver.getInstance(path.basename(file), path.resolve(file), this));
  }
}

export class LibraryResolver {
  nameOnDisk: string;
  name: string;
  path: string;
  arch: ArchResolver;
  text: string;

  constructor(name: string, soPath: string, arch: ArchResolver) {
    this.arch = arch;
    this.nameOnDisk = name;
    this.name = this.tryGetLibraryName();
    this.path = soPath;
    this.text = this.genPkgText();
  }

  static getInstance(name: string, soPath: string, arch: ArchResolver) {
    return new LibraryResolver(name, soPath, arch);
  }

  private genPkgText() {
    const matchRule = this.arch.pkg.matchRule;
    if (matchRule && matchRule.exclude.includes(this.nameOnDisk)) {
      return os.EOL;
    }

    const target = `${this.arch.pkg.name}::${this.name}`;
    let text = `if(NOT TARGET ${target})`;
    const prebuiltType = this.nameOnDisk.includes('.a') ? 'STATIC' : 'SHARED';

    text += `${os.EOL}    add_library(${target} ${prebuiltType} IMPORTED)`;
    text += `${os.EOL}    set_target_properties(${target} PROPERTIES`;

    const includeDirs: string[] = this.arch.pkg.headerPath
      .filter((dir) => fse.existsSync(dir) && fse.readdirSync(dir).length > 0)
      .map((dir) => dir.replace(/\\/g, '/'));
    if (includeDirs.length > 0) {
      text += `${os.EOL}        INTERFACE_INCLUDE_DIRECTORIES "${includeDirs.join(';')}"`;
    }

    text += `${os.EOL}        IMPORTED_LOCATION "${this.path.replace(/\\/g, '/')}"`;
    if (this.arch.pkg.linkLibrariesMap.get(this.nameOnDisk)) {
      text += `${os.EOL}        INTERFACE_LINK_LIBRARIES "${this.arch.pkg.linkLibrariesMap.get(this.nameOnDisk)}"`;
    }

    text += `)${os.EOL}endif()${os.EOL}`;
    return text;
  }

  private tryGetLibraryName() {
    const end = this.nameOnDisk.lastIndexOf('.so') > -1 ? this.nameOnDisk.lastIndexOf('.so') : this.nameOnDisk.lastIndexOf('.a');
    const start = this.nameOnDisk.toLowerCase().startsWith('lib') ? 3 : 0;
    return end > -1 ? this.nameOnDisk.substring(start, end) : this.nameOnDisk.substring(start);
  }
}
